/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package AssemblerUI;

import java.util.StringTokenizer;
import java.util.Vector;

/**
 *
 * @author mahesh
 */
public class Assembler {

    public Assembler()
    {
        vMachineInstructions = new Vector();
        
        symTab = new SymbolTable();
        opcodeTab = new OpcodeTable();
    }
    
    void AddMachineInstruction( int MachineInstruction )
    {
        vMachineInstructions.add( new Integer(MachineInstruction) );
    }
    
    void Display()
    {
        System.out.println(" Displaying the final output ");
        for( Integer MachineInstruction: vMachineInstructions )
        {
            System.out.println( "" + MachineInstruction );
        }
    }
    
    int GetInstructionFormat( String command )
    {
        /* This function takes a command as input (e.g Add, Sub etc) and returns the corresponding instruction format
         * Format 1 : Special (Used for instructions that require no operands, eg HLT etc)
         * Format 2 : 1 Register operand (eg. TRAP instruction)
         * Format 3 : 2 Register operands(eg. OR, AND instructions )
         * Format 4 : 2 Registers and one offset (eg. LDR, STR instructions)
         * Format 5 : 1 Register and 1 Immediate (eg. SIR, AIR instructions)
         * Format 6 : Only immediate operand     (eg. RFS instruction)
         * Format 7 : I/O instruction (eg. IN, OUT )
         * Format 8 : Shift/ Rotate instructions (eg. SRC, RRC )
         * Format 9 : Using index register
         */
        int format = -1;
        if( command.equalsIgnoreCase("HLT") || command.equalsIgnoreCase("NOT") )
            format = 1;
        else if( command.equalsIgnoreCase("TRAP") )
            format = 2;
        else if( command.equalsIgnoreCase("OR") || command.equalsIgnoreCase("AND") || command.equalsIgnoreCase("MUL")
                || command.equalsIgnoreCase("DIV") || command.equalsIgnoreCase("TST") )
            format = 3;
        else if( command.equalsIgnoreCase("LDR") || command.equalsIgnoreCase("STR") || command.equalsIgnoreCase("LDA") 
                || command.equalsIgnoreCase("JZ") || command.equalsIgnoreCase("JNZ") || command.equalsIgnoreCase("SOB") 
                || command.equalsIgnoreCase("ADD") || command.equalsIgnoreCase("SUB") )
            format = 4;
        else if( command.equalsIgnoreCase("SIR") || command.equalsIgnoreCase("AIR"))
            format = 5;
        else if( command.equalsIgnoreCase("RFS") )
            format = 6;
        else if( command.equalsIgnoreCase("IN") || command.equalsIgnoreCase("OUT") )
            format = 7;
        else if( command.equalsIgnoreCase("SRC") || command.equalsIgnoreCase("RRC") )
            format = 8;
        else if( command.equalsIgnoreCase("LDX") || command.equalsIgnoreCase("STX") || command.equalsIgnoreCase("JMP") 
                || command.equalsIgnoreCase("JSR") )
            format = 9;
        else
            format = -1;
        
        
        
        return format;
    }
    
    String GetCommand( String instr )
    {
        // This function extracts the command lik Add, Sub etc. from the instruction supplied
        // This assumes that the label has already been stripped.
        StringTokenizer st = new StringTokenizer( instr, " " );
        
        String retval = st.nextToken();
        
        System.out.println("\t Command is " + retval );
        
        return retval;
    }
    
    String EliminateCommand( String instr )
    {
        String retval;
        
        StringTokenizer st = new StringTokenizer( instr, " " );
        String tempStr = st.nextToken();
        
        retval = instr.substring(tempStr.length());
        
        System.out.println("\t Instr after eliminating command is " + retval );
        return retval;
    }
    
    int GetRegister( String instr )
    {
        StringTokenizer st = new StringTokenizer( instr, " ,");
        String register = st.nextToken();
        
        int retval = -1;
        if( register.equalsIgnoreCase("0") )
            retval = 0;
        else if( register.equalsIgnoreCase("1") )
            retval = 1;
        else if( register.equalsIgnoreCase("2") )
            retval = 2;
        else if( register.equalsIgnoreCase("3") )
            retval = 3;
        
        
        return retval;
    }
    
    String EliminateRegister( String instr )
    {
        StringTokenizer st = new StringTokenizer( instr, " ,");
        String register = st.nextToken();
        
        System.out.println(" Eliminating register  = " + register );
        int index = instr.indexOf(register);        
        String retval = instr.substring( index + register.length(), instr.length());
        
        return retval;
    }
    
    //int GetOffset( String instr )
    //{
        
    //}
    
    //String EliminateOffset( String instr )
    //{
        
    //}
    
    int GetImmediateOperand( String instr )
    {
        StringTokenizer st = new StringTokenizer( instr, " ,");
        
        
        String operand = st.nextToken();
        System.out.println(" immediate operand = " + operand );
        int retval;
        try
        {
            retval = Integer.parseInt(operand);
        }
        catch(Exception e)
        {
            System.out.println( "Exception wihle converting immediate operand to integer " + e );
            retval = -10000;
        }
                
        return retval;
    }
    
    int ConvertToMachineLanguageInstruction( String command, int nInstructionFormat, 
                                                int r1, int r2,
                                                int immediate, int offset,
                                                int indirectBit )
    {
        // note that the numbering described here is from the left to the right
        // and all numbering starts from 0.
        int retval = -1;
        
        int opcode = opcodeTab.GetOpcode(command);
        retval = (int)(opcode * Math.pow(2, 10));
        
        switch( nInstructionFormat )
        {
            case 1:
                // nothing to be done
                break;
            case 2:
                // we use bits 8 and 9 to denote the register.
                retval += (int)(r1 * Math.pow(2, 6));
                break;    
                
            case 3:
                // here we have two register operands
                retval += (int)(r2 * Math.pow(2, 8));
                retval += (int)(r1 * Math.pow(2, 6));
                break;    
                
            case 4:
                // here we have two registers and 1 offset
                retval += (int)(r1 * Math.pow(2, 6));
                // here r2 indicates the indexBit. indexing is enabled if it is 1
                retval += (int)(r2 * Math.pow(2, 8));
                retval += (int)(indirectBit * Math.pow(2, 9));
                retval += offset;
                break;
                
            case 5:
                
                break;
                
            case 6:
                break;
            case 7:
                break;
            case 8:
                break;
            case 9:
                break;
            default:
                break;
        }
        
        
        return retval;
    }
    
    /* Index register and condition code are handled the same way as a normal register.
     * 
     * How do we handle comments? */
    
    void Translate( String strAssemblyCode )
    {
        // run pass 1 to generate the symbol table
        symTab.GenerateSymbolTable(strAssemblyCode);
        
        
        StringTokenizer st = new StringTokenizer( strAssemblyCode, "\n" );
        
        int nLocationCounter = 0;
        
        
        while( st.hasMoreTokens() )
        {
            String instr = st.nextToken();
            System.out.println( "Instruction being assembled : " + instr );
            
            //first eliminate the label, if any
            String newInstr = symTab.EliminateSymbol(instr);
            
            System.out.println( " Instr after eliminating symbols = " + newInstr );
            
            String command = GetCommand( newInstr );
            System.out.println( " Command = " + command );
            int nInstructionFormat = GetInstructionFormat(command);
            System.out.println( " Instruction format = " + nInstructionFormat );
            newInstr = EliminateCommand( newInstr );
            System.out.println( " Instr after eliminating command = " + newInstr );
            
            // now we need to obtain the next operands based on the instruction format
            int r1 = -1, r2 = -1, offset = -10000, immediate = -10000;
            switch( nInstructionFormat )
            {
                case 1:
                    // HLT, NOT : so no operands, just break
                    break;
                case 2:
                    // TRAP : Will contain a register operand
                    r1          = GetRegister( newInstr );
                    break;
                    
                case 3:
                     // 2 Register operands(eg. OR, AND instructions )
                    r1          = GetRegister( newInstr );
                    newInstr    = EliminateRegister(newInstr);
                    r2          = GetRegister( newInstr );
                    newInstr    = EliminateRegister(newInstr);
                    break;
                    
                case 4:
                    // 2 Registers and one offset (eg. LDR, STR instructions)
                    r1          = GetRegister( newInstr );
                    newInstr    = EliminateRegister(newInstr);
                    r2          = GetRegister( newInstr );
                    newInstr    = EliminateRegister(newInstr);
                    //offset      = GetOffset( newInstr );
                    break;
                    
                case 5:
                    // 1 Register and 1 Immediate (eg. SIR, AIR instructions)
                    r1          = GetRegister( newInstr );                    
                    newInstr    = EliminateRegister( newInstr );                    
                    immediate   = GetImmediateOperand( newInstr );
                    
                    break;
                    
                case 6:
                    // Only immediate operand     (eg. RFS instruction)
                    immediate   = GetImmediateOperand( newInstr );
                    break;
                
                case 7:
                    // IO instruction (eg. IN, OUT instruction)
                    break;
                
                case 8:
                    break;
                
                case 9:
                    break;
                default:
                    break;
            }
            
            // if there is still something left in the instruction, we have stray symbols
            if( newInstr.length() > 0 )
            {
                System.out.println(" Error: Seems like you have extra stuff on line " + nLocationCounter );                
            }
            else
            {
                // display the operands obtained so far                
                System.out.println( " operands are " + r1 + " " + r2 + " " + offset + " " + immediate );
                int machineLanguageInstr = ConvertToMachineLanguageInstruction(
                                            command, nInstructionFormat, 
                                            r1, r2, 
                                            immediate, offset);
                
                System.out.println(" the final machine language instr = "+ machineLanguageInstr );
            }
            
            nLocationCounter++;
        }
        Display();
    }
    
    private SymbolTable symTab;
    private OpcodeTable opcodeTab;
    private Vector<Integer> vMachineInstructions;
}
